package org.zpmis.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.zpmis.domain.role.model.SysPermission;
import org.zpmis.domain.role.service.GatewayApiService;
import org.zpmis.gateway.configurer.JWTConfig;
import org.zpmis.gateway.constant.UserConstants;
import org.zpmis.gateway.utils.FilterUtil;
import org.zpmis.gateway.utils.RedisUtil;
import org.zpmis.gateway.utils.ResultCode;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
public class AuthGatewayFilter implements GatewayFilter, Ordered {

    @Resource
    private RedisUtil redisUtil;
    @Reference
    private GatewayApiService gatewayApiService;

    //拦截请求静态文件 防止错误的请求404的问题
    private static String FAVICON = "/favicon.ico";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 自定义返回格式
        Map<String, Object> resultMap = new HashMap<>(3);
        //int i = 1/0;
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String methodValue = request.getMethodValue();
        //获取请求uri地址
        String uri = request.getURI().getPath();
        String ipAddr = FilterUtil.getIpAddr(request);
        String url = request.getURI().toString();
        log.info("[AuthGatewayFilter]----请求路径：{},请求方式:{},请求的接口地址：{},请求的ip:{}",url,methodValue,uri,ipAddr);
        //String path = request.getPath().toString();
        if(FAVICON.equals(uri)){
            resultMap.put("code", ResultCode.RESOURCE_NOT_FOUND.getCode());
            resultMap.put("msg", ResultCode.RESOURCE_NOT_FOUND.getMsg());
            return FilterUtil.customError(exchange,resultMap);
        }
        //放行不需要登录拦截的接口
        if(JWTConfig.antMatchers.contains(uri)){
            return chain.filter(exchange);
        }
        String tokenHeader = request.getHeaders().getFirst(JWTConfig.tokenHeader);
        log.info("[AuthGatewayFilter]----当前请求携带的tokenHeader:{}",tokenHeader);

        //token为空说明未登录，不放行
        if (StringUtils.isBlank(tokenHeader)){
            //用户未登录
            resultMap.put("code", ResultCode.USER_NOT_LOGIN.getCode());
            resultMap.put("msg", ResultCode.USER_NOT_LOGIN.getMsg());
            return FilterUtil.customError(exchange,resultMap);
        }
            //根据token的签发者 判断token来源 从而取不同的redisKey
            String USER_INFO_REDIS_KEY;
            String USER_TOKEN_REDIS_KEY;
            // 截取JWT前缀
            String token = tokenHeader.replace(JWTConfig.tokenPrefix, "");
            // 解析JWT
            Claims claims = null;
            try {
                claims = Jwts.parser()
                        .setSigningKey(JWTConfig.secret)
                        .parseClaimsJws(token)
                        .getBody();
            } catch (ExpiredJwtException e) {
                log.error("[AuthGatewayFilter]---登录过期异常e:",e);
                //登录已过期
                resultMap.put("code", ResultCode.USER_LOGIN_TIMEOUT.getCode());
                resultMap.put("msg", ResultCode.USER_LOGIN_TIMEOUT.getMsg());
                return FilterUtil.customError(exchange,resultMap);
            } catch (UnsupportedJwtException e) {
                log.error("[AuthGatewayFilter]---登录Token校验失败异常e:",e);
                //token校验失败
                resultMap.put("code", ResultCode.USER_LOGIN_TOKEN_VALIED_ERROR.getCode());
                resultMap.put("msg", ResultCode.USER_LOGIN_TOKEN_VALIED_ERROR.getMsg());
                return FilterUtil.customError(exchange,resultMap);
            } catch (MalformedJwtException e) {
                log.error("[AuthGatewayFilter]---登录Token格式错误异常e:",e);
                //token格式错误
                resultMap.put("code", ResultCode.USER_LOGIN_TOKEN_FORMAT_ERROR.getCode());
                resultMap.put("msg", ResultCode.USER_LOGIN_TOKEN_FORMAT_ERROR.getMsg());
                return FilterUtil.customError(exchange,resultMap);
            } catch (SignatureException e) {
                log.error("[AuthGatewayFilter]---登录Token签名异常e:",e);
                //token签名错误
                resultMap.put("code", ResultCode.USER_LOGIN_TOKEN_SIGN_ERROR.getCode());
                resultMap.put("msg", ResultCode.USER_LOGIN_TOKEN_SIGN_ERROR.getMsg());
                return FilterUtil.customError(exchange,resultMap);
            } catch (IllegalArgumentException e) {
                log.error("[AuthGatewayFilter]---登录Token不合法异常e:",e);
                //token参数不合法
                resultMap.put("code", ResultCode.PARAM_ERROR.getCode());
                resultMap.put("msg", ResultCode.PARAM_ERROR.getMsg());
                return FilterUtil.customError(exchange,resultMap);
            }

            try {
                // 获取用户名 小程序登录没有用户名
                String username = claims.getSubject();
                //获取用户id
                String userId=claims.getId();
                //获取签发者 判断是前台登陆还是后台登录
                String issuer = claims.getIssuer();
                //根据签发者 获取对应的redis 前缀
                USER_INFO_REDIS_KEY = UserConstants.getUserInfoRedisKey(issuer);
                USER_TOKEN_REDIS_KEY = UserConstants.getUserTokenRedisKey(issuer);
                if(StringUtils.isEmpty(USER_INFO_REDIS_KEY) || StringUtils.isEmpty(USER_TOKEN_REDIS_KEY)){
                    log.error("[AuthGatewayFilter]---登录Token签发者不合法issuer:{}:",issuer);
                    //token参数不合法
                    resultMap.put("code", ResultCode.PARAM_ERROR.getCode());
                    resultMap.put("msg", ResultCode.PARAM_ERROR.getMsg());
                    return FilterUtil.customError(exchange,resultMap);
                }
                //获取redis中存的用户token信息
                Object redisToken = redisUtil.get(String.format(USER_INFO_REDIS_KEY, userId));
                if(redisToken == null || !redisToken.equals(tokenHeader)){
                    log.error("[AuthGatewayFilter]---redis登录过期RedisToken:{}:",redisToken);
                    // 登录已过期
                    resultMap.put("code", ResultCode.USER_LOGIN_TIMEOUT.getCode());
                    resultMap.put("msg", ResultCode.USER_LOGIN_TIMEOUT.getMsg());
                    return FilterUtil.customError(exchange,resultMap);
                }
                //获取redis中存的用户信息
                Object loginUser = redisUtil.get(String.format(USER_TOKEN_REDIS_KEY, tokenHeader));
                if(loginUser == null){
                    log.error("[AuthGatewayFilter]---redis登录过期RedisLoginUser:{}:",loginUser);
                    // 登录已过期
                    resultMap.put("code", ResultCode.USER_LOGIN_TIMEOUT.getCode());
                    resultMap.put("msg", ResultCode.USER_LOGIN_TIMEOUT.getMsg());
                    return FilterUtil.customError(exchange,resultMap);
                }
                Date jWtExpiration = claims.getExpiration();
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String formatExpiration = simpleDateFormat.format(jWtExpiration);
                long redisTokenExpire = redisUtil.getExpire(String.format(USER_INFO_REDIS_KEY, userId));
                long redisInfoExpire = redisUtil.getExpire(String.format(USER_TOKEN_REDIS_KEY, tokenHeader));
                log.info("[AuthGatewayFilter]---JWTexpiration过期时间剩余:{},redisTokenExpire:{}过期时间剩余:-->:{}s,redisInfoExpire过期时间剩余:-->:{}s",formatExpiration,String.format(USER_INFO_REDIS_KEY, userId),redisTokenExpire,redisInfoExpire);
                //判断当前接口的权限
                //查询具体某个接口的权限
                List<SysPermission> sysPermissionList = gatewayApiService.getPermissionListByUri(uri);
                if (sysPermissionList == null || sysPermissionList.size() == 0 || username.equals(UserConstants.ROOT)) {
                    //请求路径没有配置权限，表明该请求接口可以任意访问
                    return chain.filter(exchange);
                }else {
                    //请求路径有权限配置，需要判断该用户是否拥有该接口的访问权限
                    //该接口需要的权限
                    List<String> uriNeedPermissions = sysPermissionList.stream().map(SysPermission::getPermissionCode).collect(Collectors.toList());
                    // 获取用户拥有的权限
                    List<String> userPermissions = new ArrayList<>();
                    String authority = claims.get("authorities").toString();
                    if(!StringUtils.isEmpty(authority) && !"null".equals(authority)){
                        List<String> authorityList = JSONObject.parseObject(authority, List.class);
                        authorityList.stream().forEach(userPermission ->{
                            userPermissions.add(userPermission);
                        });
                    }
                    //当这两个集合中不存在公共元素的时候返回true 存在公共元素就返回false
                    if(!Collections.disjoint(userPermissions,uriNeedPermissions)){
                        //用户权限列表中包含接口权限 可以访问
                        return chain.filter(exchange);
                    }else{
                        //无权限访问
                        resultMap.put("code", ResultCode.NO_PERMISSION.getCode());
                        resultMap.put("msg", ResultCode.NO_PERMISSION.getMsg());
                        return FilterUtil.customError(exchange,resultMap);
                    }
                }
            }catch (Throwable e){
                log.error("[AuthGatewayFilter]---系统内部错误异常e:",e);
                //系统内部错误
                resultMap.put("code", ResultCode.ERROR.getCode());
                resultMap.put("msg", ResultCode.ERROR.getMsg());
                return FilterUtil.customError(exchange,resultMap);
            }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
